/**
 * @author : 陆楚良 (Email:lu_chuliang@sina.com|QQ:874449204)
 * @version: 1.0.2
 * @license: http://www.apache.org/licenses/LICENSE-2.0
 */
// 说明：
//   在源码文件中，使用   // <include: filename.js>   的注释方法进行包含另一文件，文件名支持使用 * 通配符（大小写敏感）
//   在源码文件中，使用   /*<include-string: filename.js>*/   的标识将指定文件内容转化为字符串插入源码
//   在源码文件中，使用   /*<include-html: filename.js>*/     的标识将指定文件内容转化为每行去除首尾空白的字符串插入源码
//   在源码主文件中，多行注释中，某行首字符为 * 时，该行内可以使用{date}将当前时间写入源码
//   只有源码主文件支持在第一行代码中写入注释   // <out: output.js>  声明打包后的文件存放路径和{date}声明写入时间



"use strict";

var path  = require('path')
  , fs    = require('fs')
;
function print(msg){
    console.log(msg)
}

const RE_OUTFILE = /^\s*\/\/ *< *out(-compress)? *:(.+?)>/i;   // 输出文件
const RE_DATE    = /^\s*\*.*\{date\}/i;                        // 时间
const RE_INDENT  = /^\s*/;                                     // 缩进
const RE_INCLUDE = /^\s*\/\/ *< *include *:(.+?)>/i;           // include
const RE_STRING  = /('' *|"" *|)(\/\* *< *include-string *:(.+?)> *\*\/)/i;  // include-string
const RE_HTML    = /('' *|"" *|)(\/\* *< *include-html *:(.+?)> *\*\/)/i     // include-html
const RE_SPACE   = /^\s*$/;                                    // 空白行
const RE_ENDSPACE= /\r?\n\s*$/;                                // 结尾空白
const RE_DOC     = /^ *\/\*[\s\S]+?\*\//;                      // 头注释

class Generate{
    constructor(filename){
        var date = new Date()
          , time = date.getTime()
        ;
        this.date= date.getFullYear()
                 + '-'+this.pad(date.getMonth()+1)
                 + '-'+this.pad(date.getDate())
                 + ' '+this.pad(date.getHours())
                 + ':'+this.pad(date.getMinutes())
                 + ':'+this.pad(date.getSeconds())
        ;
        this._compress = null;
        filename = path.normalize(filename);
        print("  input: "+filename);
        var dirname = path.dirname(filename)+path.sep
          , lines   = this.readlines(filename)
          , outfile = lines.shift().match(RE_OUTFILE)
          , indent  = ""
          , lnum    = 0
          , output  = ""
        ;
        if(outfile){
            if(outfile[1]){
                this._compress = path.extname(outfile[2].trim());
            }
            outfile  = path.normalize(dirname + outfile[2].trim());
            print(" output: "  + outfile);
            var ret  = this.forEach(lines, dirname, indent, lnum, true);
            if(this._compress!==null){
                // 压缩
                print("...Compressing Code...");
                var doc = ret.content.match(RE_DOC);
                ret.content = (doc?doc+"\r\n":"")+this.compress(ret.content);
            }
            this.mkdirsSync(path.dirname(outfile));
            fs.writeFileSync(outfile, ret.content.replace(RE_ENDSPACE, ""));
            print("Success: " + outfile);
        }else{
            print("\n  Error: Output file does not exist.");
        }
        print("\n  Takes: " + Number((((new Date().getTime()) - time)/1000).toFixed(3)) + "s  [ " + this.date + " ]");
    }
    /**
     * 读取文件
     */
    read(filename){
        // 注：会优化文件结尾空白行
        return fs.readFileSync(filename).toString().replace(RE_ENDSPACE, "");
    }
    /**
     * 按行读取文件
     */
    readlines(filename){
        return this.read(filename).split(/\r?\n/g);
    }
    /**
     * 创建文件夹
     * @param {string} dirpath 路径
     */
    mkdirsSync(dirpath){
        if (!fs.existsSync(dirpath)) {
            var pathtmp;
            dirpath.split(path.sep).forEach(function(dirname) {
                pathtmp = pathtmp ? path.join(pathtmp, dirname) : dirname;
                fs.existsSync(pathtmp) || fs.mkdirSync(pathtmp);
            });
        }
    }
    /**
     * 遍历行
     * @param  {array}   lines   行
     * @param  {string}  dirname 相对目录
     * @param  {string}  indent  缩进
     * @param  {number}  lnum    行数
     * @param  {boolean} ismain  是否是主文件
     * @return {object}          {content:内容, lnum:行数}
     */
    forEach(lines, dirname, indent, lnum, ismain){
        var output = "",length=lines.length;
        lines.forEach((line)=>{
            line += "\r\n";
            lnum += 1;
            var match;
            if(ismain && line.trim().substr(0,1)=="*"){
                // 主文件支持{date}插入build时间
                output += indent + line.replace("{date}", this.date);
            }else if(match = line.match(RE_INCLUDE)){
                // 匹配 <include: filename.js>
                var indent2 = indent+line.match(RE_INDENT);
                var filename= path.normalize(dirname + match[1].trim());
                if(filename.indexOf("*")>-1){
                    // 存在*通配符，搜索文件列表批量导入
                    var search_dir = path.dirname(filename)+path.sep
                      , search_str = filename.split(path.sep).pop()
                      , search_re  = new RegExp(this.escape(search_str), "i")
                      , p          = false
                    ;
                    fs.readdirSync(search_dir).forEach((file)=>{
                        if(search_re.test(file) && fs.lstatSync(search_dir+file).isFile()){
                            var ret = this.include(search_dir+file, indent2, lnum);
                            output += indent+line.replace(match[0], match[0]+" line: "+lnum+"-"+ret.lnum).replace(search_str, file)+ret.content;
                            lnum    = ret.lnum+1;
                            p       = true;
                        }
                    });
                    if(p){
                        // 减去多算出来的1条行数
                        lnum -= 1;
                    }
                }else{
                    var ret = this.include(filename, indent2, lnum);
                    output += indent+line.replace(match[0], match[0]+" line: "+lnum+"-"+ret.lnum)+ret.content;
                    lnum = ret.lnum;
                }
            }else if(match = line.match(RE_STRING)){
                var filename= path.normalize(dirname + match[3].trim());
                output += indent+line.replace(match[0], match[2]+this.include_string(filename));
            }else if(match = line.match(RE_HTML)){
                var filename= path.normalize(dirname + match[3].trim());
                output += indent+line.replace(match[0], match[2]+this.include_html(filename));
            }else{
                if(RE_SPACE.test(line)){
                    // 优化空白行
                    output += "\r\n";
                }else{
                    // 输出原文
                    output += indent + line;
                }
            }
        });
        return {content:output, lnum:lnum};
    }
    /**
     * 转义正则式
     */
    escape(string){
        return string.replace(/\.|\\|\+|\*|\?|\[|\^|\]|\$|\(|\)|\{|\}|\=|\!|\<|\>|\||\:|\-/g, (search)=>{
            return search==="*" ? ".+?" : "\\"+search;
        });
    }
    /**
     * 转义字符串
     */
    repr(string){
        return "\""+string.replace(/([\'\"\\])/g,"\\$1").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t")+"\"";
    }
    /**
     * 个位数补0
     */
    pad(n){
        return n < 10 ? "0"+n : n;
    }
    /**
     * include文件
     * @param  {string}  filename 文件路径
     * @param  {string}  indent   缩进
     * @param  {number}  lnum     行数
     * @return {object}           {content:内容, lnum:行数}
     */
    include(filename, indent, lnum){
        filename    = path.normalize(filename);
        print("include: " + filename);
        var dirname = path.dirname(filename)+path.sep
          , lines   = this.readlines(filename)
          , output  = ""
        ;
        return this.forEach(lines, dirname, indent, lnum);
    }
    /**
     * include-string
      * @param  {string}  filename 文件路径
      * @return {string}
     */
    include_string(filename){
        print(" string: " + filename);
        return this.repr(this.read(filename));
    }
    /**
     * include-html
      * @param  {string}  filename 文件路径
      * @return {string}
     */
    include_html(filename){
        print("   html: " + filename);
        var output = "";
        this.readlines(filename).forEach((line)=>{
            output += line.trim();
        });
        return this.repr(output);
    }
    /**
     * js压缩
     */
    compress(jscode){
        try{
            var result = require("uglify-js").minify(jscode, {
                fromString : true
                // compress   : {
                //     sequences     : true,  // join consecutive statemets with the “comma operator”
                //     properties    : true,  // optimize property access: a["foo"] → a.foo
                //     dead_code     : true,  // discard unreachable code
                //     drop_debugger : true,  // discard “debugger” statements
                //     unsafe        : false, // some unsafe optimizations (see below)
                //     conditionals  : true,  // optimize if-s and conditional expressions
                //     comparisons   : true,  // optimize comparisons
                //     evaluate      : true,  // evaluate constant expressions
                //     booleans      : true,  // optimize boolean expressions
                //     loops         : true,  // optimize loops
                //     unused        : true,  // drop unused variables/functions
                //     hoist_funs    : true,  // hoist function declarations
                //     hoist_vars    : false, // hoist variable declarations
                //     if_return     : true,  // optimize if-s followed by return/continue
                //     join_vars     : true,  // join var declarations
                //     cascade       : true,  // try to cascade `right` into `left` in sequences
                //     side_effects  : true,  // drop side-effect-free statements
                //     warnings      : true,  // warn about potentially dangerous optimizations/code
                // }
            });
            return result.code;
        }catch (e){
            print(e);
            return "";
        }
    }
}
process.argv.splice(2).forEach((main)=>{
    new Generate(main);
});
